home *** CD-ROM | disk | FTP | other *** search
/ SGI Freeware 2002 November / SGI Freeware 2002 November - Disc 1.iso / dist / fw_exmh.idb / usr / freeware / lib / exmh-2.5 / addr.tcl.z / addr.tcl
Text File  |  2002-07-08  |  46KB  |  1,412 lines

  1. ############################################################################
  2. #  Insidious Mail DB
  3. #
  4. #-------------------
  5. # Copyright 1996, Xerox Corporation.  All Rights Reserved.
  6. # License is granted to copy, to use, and to make and to use derivative works for
  7. # research and evaluation purposes, provided that the copyright notice and
  8. # this license notice is included in all copies and any derivatives works and in
  9. # all  related documentation.  Xerox grants no other licenses expressed or
  10. # implied and the licensee acknowleges that Xerox have no liability for
  11. # licensee's use or for any derivative works made by licensee. The Xerox
  12. # names shall not be used in any advertising or the like without their written
  13. # permission.
  14. # This software is provided AS IS.
  15. # XEROX CORPORATION DISCLAIMS AND LICENSEE
  16. # AGREES THAT ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
  17. # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  18. # NOTWITHSTANDING ANY OTHER PROVISION CONTAINED HEREIN, ANY LIABILITY FOR DAMAGES
  19. # RESULTING FROM THE SOFTWARE OR ITS USE IS EXPRESSLY DISCLAIMED, INCLUDING
  20. # CONSEQUENTIAL OR ANY OTHER INDIRECT DAMAGES, WHETHER ARISING IN CONTRACT, TORT
  21. # (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, EVEN IF XEROX CORPORATION
  22. # IS ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
  23. #---------
  24. # This package saves the e-mail address of everyone you get mail from, and lets
  25. # you send mail back with only a partial address.
  26. #
  27. # When you type part of an address in the To: or Cc: field, Ctrl-TAB will
  28. # attempt to complete the address.
  29. #
  30. # The concept owes a lot to the Gnu Emacs package "BBDB" by Jamie Zawinski 
  31. # (jwz@netscape.com) but this implementation is strictly my own.  Thanks, Jamie!
  32. #
  33. #
  34. # A neat feature of the browser (and entry editor) is that you can pop up (a la Clip)
  35. # the last message you got from that person.  Be careful, though; if that message was 
  36. # deleted or the folder was packed since that message arrived, it will fail.
  37. #
  38. # I've been using this (or earlier versions) for about 2 years now and my database 
  39. # is about 5000 entries; I find it EXTERMELY useful when I cannot quite remember the email
  40. # address of that guy I got a message from 6 months ago and need to reply to, but
  41. # I remember it was "Ted something".
  42. #
  43. # It takes a little while to load the browser window (at least for me, sorting 5000 
  44. # strings and then inserting them into a listbox takes a while), but then it "stays"
  45. # even if you dismiss it so it's not too painful.
  46. #
  47. # Enjoy; if you find this useful please let me know; if you make it better 
  48. # please send me the code.
  49. #
  50. #   --Berry Kercheval, Xerox PARC, March 1996 (kerch@parc.xerox.com)
  51. #########################################################################
  52.  
  53. #
  54. # These variables control the setup
  55. #
  56. set addrVersion {$Revision: 1.14 $}
  57.  
  58. # Addr_debug, if ==1, enables printing messages while this stuff runs.
  59. # Set Addr_debug 1 in your user.tcl before invoking Addr_Init to enable debug messages
  60. if {0 == [info exists Addr_debug]} {
  61.     set Addr_debug 0
  62. }
  63.  
  64. #
  65. # Addr_Init loads the database file at startup time, and arranges the 
  66. # partial-address-expansion keybinding.
  67. #
  68. proc Addr_Init {} {
  69.     global env
  70.     global addrFile
  71.     global homeDir
  72.     global addr_list
  73.  
  74.     # addrFile is the name of the file the address database is kept in
  75.     set addrFile "exmh_addrs"
  76.     
  77.     # homeDir is the directory the database is kept in.
  78.     set homeDir "$env(HOME)/.exmh"
  79.  
  80.     # tell exmh about our preference items and initialize them
  81.     Preferences_Add "Address Database" "These settings affect the behavior of the address database.
  82.     See also the key binding for \"addrexpand\" that is set
  83.     in the Bindings... dialog Simple Edit." {
  84.         {
  85.             addr_db(enabled)
  86.             addressdbEnabled
  87.             ON
  88.             "Automatic address saving"
  89.             "If set, From addresses are remembered and available in an address browser."
  90.         }
  91.         {
  92.             addr_db(hideexcluded)
  93.             addressdbHideExcluded
  94.             ON
  95.             "Hide excluded addresses"
  96.             "If set, addresses excluded from the expansion process will not be displayed by the Address DB Browser."
  97.         }
  98.         {
  99.             addr_db(checkpoint_on_folder_change) 
  100.             addressdbFolderChangeCheckpoint
  101.             ON
  102.             "Checkpoint on Folder Change"
  103.             "If set, Exmh will save your address file whenever you visit a new folder."
  104.         }
  105.         {
  106.             addr_db(searchlist)
  107.             addressdbSearchlist
  108.             {Addr_FullNameMatch Addr_Lookup Alias_Lookup LDAP_Lookup}
  109.             "Expand methods to use"
  110.             "A list of TCL procs, separated by spaces, which will be called sequentially to try to expand the address.  Valid choices are \"Addr_FullNameMatch\" to search full names, \"Addr_Lookup\" to search for mail addresses, \"Alias_Lookup\" to search your MH/exmh alias list, and LDAP_Lookup to use an LDAP server."
  111.         }
  112.         {
  113.             addr_db(ldap_server)
  114.             addressdbLDAPServer
  115.             {}
  116.             "LDAP Server"
  117.             "The server to send LDAP queries to."
  118.         }
  119.         {
  120.             addr_db(ldap_searchbase)
  121.             addressdbLDAPSearchBase
  122.             {}
  123.             "LDAP Search Root"
  124.             "The root under which to conduct LDAP searches."
  125.         }
  126.     {
  127.         addr_db(ldap_encoding)
  128.         addressdbLDAPEncoding
  129.         {utf-8}
  130.         "LDAP Encoding"
  131.         "The character encoding used by the LDAP server."
  132.     }
  133.         {
  134.             addr_db(filter_regexp)
  135.             addressdbFilterRegexp
  136.             {}
  137.             "Regular expression filter"
  138.             "If set, addresses matching this regular expression pattern will not be saved in the database."
  139.         }
  140.         {
  141.             addr_db(skip_folders)
  142.             addressdbFoldersSkip
  143.             {}
  144.             "Folders to ignore"
  145.             "A list of one or more folders separated by spaces.  Exmh will not save addresses from the mail in the folders in this list. An empty list will allow Exmh to add addresses from every folder.  Groups of folders may be specified using * as a wild card anywhere in a folder name."
  146.         }
  147.         {
  148.             addr_db(filter_alternate_mailboxes) 
  149.             addressdbFilterAltMailboxes
  150.             ON
  151.             "Ignore alternate mailboxes"
  152.             "If set, addresses that match the names specified as \"Alternate mailboxes\" in your MH profile will not be saved."
  153.         }
  154.         {
  155.             addr_db(key_force_save)
  156.             addressdbForceSave 
  157.             <Control-Tab>
  158.             "Key to save an address"
  159.             "Key which, if pressed, will cause the address from the current message to be stored regardless of any filtering specified.  This key is only active in the main exmh window so it may be the same as the \"Key to expand addresses\" without conflict.  Pressing this key stores an address in the database even if it would have been filtered (not stored) due to matching \"Regular expression filter\", \"Folders to ignore\", or one of your alternate mailboxes."
  160.         }
  161.         {
  162.             addr_db(standard_address_format)
  163.             addressdbStandardFormat
  164.             ON
  165.             "Use \"address (Full Name)\" Format"
  166.             "If on, use \"address (Full Name)\" format for expanded addresses.  Otherwise, use \"Full Name <address>\" format."
  167.         }
  168.         {
  169.             addr_db(remove_entries)
  170.             addressdbRemoveEntries
  171.             OFF
  172.             "Remove Old Entries"
  173.             "If on, remove old entries from the database"
  174.         }
  175.         {
  176.             addr_db(remove_days)
  177.             addressdbRemoveDays
  178.             {}
  179.             "Days Until Removal"
  180.             "Number of days until inactive entry is removed"
  181.         }
  182.         {
  183.             addr_db(remove_invalid_date)
  184.             addressdbRemoveInvalidDate
  185.             OFF
  186.             "Remove Invalid Date"
  187.             "If on, delete any entry with a non-null, but invalid date"
  188.         }
  189.     }
  190.  
  191.     #addr_db is an array used for keeping state.
  192.     #make this an array from the get-go and set the default pref...
  193.     global addr_db
  194.     set addr_db(init) 1;  
  195.     if ![info exists addr_db(curmethod)]            {set addr_db(curmethod) 0}
  196.     if ![info exists addr_db(laststring)]           {set addr_db(laststring) ""}
  197.     if ![info exists addr_db(lastfound)]            {set addr_db(lastfound) ""}
  198.     if ![info exists addr_db(changed)]              {set addr_db(changed) 0}
  199.     if ![info exists addr_db(filterstring)]         {set addr_db(filterstring) ""}
  200.     if ![info exists addr_db(remove_entries)]       {set addr_db(remove_entries) 0}
  201.     if ![info exists addr_db(remove_days)]          {set addr_db(remove_days) ""}
  202.     if ![info exists addr_db(remove_invalid_date)]  {set addr_db(remove_invalid_date) 0}
  203.  
  204.     trace variable addr_db(hideexcluded) w Addr_Browse_Exclude_Change
  205.     Addr_LoadDB
  206. }
  207. #
  208. # Hook_MsgShow is called when exmh displays a message.  We parse out the 
  209. # From: header and call Addr_Save to update the entry in the database.
  210. #
  211. proc Hook_MsgShowAddr {path headervar } {
  212.     upvar $headervar header
  213.     global addr_db
  214.  
  215.     set addr_db(last_seen) [list $path $header(0=1,hdr,from) $header(0=1,hdr,date)]
  216.     if {! $addr_db(enabled)} {
  217.         return
  218.     }
  219.     Addr_Save [MsgParseFrom $header(0=1,hdr,from)] $path \
  220.             $header(0=1,hdr,from)       $header(0=1,hdr,date)
  221. }
  222.  
  223. #
  224. # Hook_FolderChange is called when a new folder is visited.  Save the 
  225. # database then too.
  226. #
  227. proc Hook_FolderChangeAddr {newfolder} {
  228.     global addr_db
  229.  
  230.     if {$addr_db(changed) && $addr_db(checkpoint_on_folder_change)} {
  231.         Addr_SaveFile
  232.     }
  233. }
  234.  
  235.  
  236. #
  237. # Hook_CheckPoint is called when exmh checkpoints its state.  Save the 
  238. # database here.
  239. #
  240. proc Addr_CheckPoint {} {
  241.     Addr_SaveFile
  242. }
  243.  
  244. #####
  245. #
  246. # Address_Init and the address Hook procedures are moved to extrasInit.tcl
  247. #
  248. ####
  249.  
  250. ########################################################################
  251. #
  252. # This one is bound to a key of the user's choosing to force-save 
  253. # the from address of the current message (info stored in last-seen by
  254. # the Hook_MsgShow routine above.
  255. #
  256. proc Address_Save {} {
  257.     global addr_db
  258.  
  259.     AddrDebug "Force save $addr_db(last_seen)"
  260.  
  261.     Addr_Save [MsgParseFrom [lindex $addr_db(last_seen) 1]] \
  262.             [lindex $addr_db(last_seen) 0] \
  263.             [lindex $addr_db(last_seen) 1] \
  264.             [lindex $addr_db(last_seen) 2] \
  265.             "force"
  266. }
  267. # SaveTo saves the current address on the to line
  268. #
  269. proc SaveTo { w } {
  270.     global addr_db
  271.  
  272.     Exmh_Status "SaveTo: w=$w"
  273.     set line [string trim [$w get {insert linestart} {insert lineend}]]
  274.     ##  AddrDebug "  got line \"$line\""
  275.     # Only allows expansion on addressable header lines.
  276.     if [regexp -nocase {^(to: *|resent-to: *|cc: *|resent-cc: *|bcc: *|dcc: *)(.*)} $line t0 t1 t2] {
  277.         ##  AddrDebug  "  matched! keep is \"$t1\", partial name=\"$t2\""
  278.         if [regexp -indices ",?.*, *" $t2 t0] {
  279.             set t0 [lindex $t0 end]
  280.             ##  AddrDebug "got comma at $t0"
  281.             set t3 [string range $t2 0 $t0]
  282.             append t1 $t3
  283.             set t2 [string range $t2 [expr $t0 + 1] end]
  284.             ##  AddrDebug "  multi, will keep \"$t1\", new partial name=\"$t2\""
  285.         }
  286.         # Save address ($t2) 
  287.         Addr_Save [MsgParseFrom $t2] "NEW" $t2 "NULL" "force"
  288.  
  289.     } else {
  290.         Exmh_Status "Error in name expansion: not on To: field"
  291.         return
  292.     }
  293. }
  294.  
  295. ########################################################################
  296. #
  297. # These are the "real" database procs.
  298. #
  299.  
  300. #
  301. # This is "magically" executed when exmh is setting up 
  302. # keystroke bindings for the main window
  303. #
  304. proc Addr_Bindings { w } {
  305.     global addr_db
  306.     if {$addr_db(key_force_save) != ""} {
  307.         AddrDebug "binding for $w"
  308.         Bind_Key $w $addr_db(key_force_save) {Address_Save ; break}
  309.     }
  310. }
  311.  
  312. #
  313. # Addr_LoadDB does the real work to load the database file.
  314. #
  315. proc Addr_LoadDB { {ldmsg ""} } {
  316.     global addr_db
  317.     global addrFile
  318.     global homeDir
  319.     global addr_list
  320.  
  321.     AddrDebug "AddrDB: loading database..."
  322.     catch {source $homeDir/$addrFile}
  323.     set addr_db(changed) 0
  324.     if {[array size addr_list] == 0} {
  325.         set addr_list(noone@nowhere.nada.zip) 1; #null array, put in a dummy
  326.     }
  327.     AddrDebug "AddrDB: loading database...done."
  328.     Addr_Browse_LoadListbox $ldmsg
  329. }
  330. #
  331. # Addr_SaveFile saves the database into a unix file 
  332. #
  333. proc Addr_SaveFile { {force 0} } {
  334.     global addr_list addr_db
  335.     global addrFile
  336.     global homeDir
  337.  
  338.     if {0 == $addr_db(changed) && 0 == $force} return
  339.  
  340.     Exmh_Status "Saving address database..."
  341.     if {$addr_db(remove_days) == ""} {
  342.         set expiration 0
  343.      
  344.     } else {
  345.         set expiration [expr [clock seconds] - (60*60*24*$addr_db(remove_days))]
  346.     }
  347.     set fd [open "$homeDir/.exmh_addr_tmp" w]
  348.     foreach i [array names addr_list] {
  349.        if {[catch {if {$addr_db(remove_entries) == 1 &&
  350.                        $addr_db(enabled) == 1 &&
  351.                        $expiration > 0 &&
  352.                        [lindex $addr_list($i) 1] != "" &&
  353.                        [clock scan [lindex $addr_list($i) 1]] < $expiration} {
  354.                       unset addr_list($i) 
  355.                   } else {
  356.                      puts $fd [list set addr_list($i) $addr_list($i)]
  357.                   }}]!= 0} {
  358.           if {$addr_db(remove_invalid_date)} {
  359.              unset addr_list($i)
  360.           } else {
  361.              puts $fd [list set addr_list($i) $addr_list($i)]
  362.           }
  363.        }
  364.     }
  365.     close $fd
  366.     #  the first time the address file won`t exist yet...
  367.     if [file exists $homeDir/$addrFile] {
  368.         Mh_Rename $homeDir/$addrFile $homeDir/$addrFile.bak
  369.     }
  370.     Mh_Rename $homeDir/.exmh_addr_tmp $homeDir/$addrFile 
  371.     set addr_db(changed) 0
  372.     Exmh_Status "Saving address database...done."
  373. }
  374.  
  375. #
  376. # Field level extract and set routines
  377. #
  378.  
  379. proc Addr_Entry_IsExcluded {key} {
  380.     global addr_list
  381.  
  382.     set excluded 0
  383.     if [catch {set item $addr_list($key)} err] {
  384.         Exmh_Status "Address DB: Addr_Entry_IsExcluded lookup error for $key"
  385.     } else {
  386.         if {1==[lindex $item 4]} { set excluded 1 }
  387.     }
  388.     return $excluded
  389. }
  390.  
  391. proc Addr_Entry_SetExcluded {key} {
  392.     global addr_list addr_db
  393.  
  394.     if [catch {set item $addr_list($key)} err] {
  395.         Exmh_Status "Address DB: Addr_Entry_SetExcluded lookup error for $key"
  396.     } else {
  397.         if {5 == [llength $item]} {
  398.             set addr_list($key) "[lrange $item 0 3] 1"
  399.         } else {
  400.             set addr_list($key) "$item 1"
  401.         }
  402.         set addr_db(changed) 1
  403.     }
  404.     return 
  405. }
  406.  
  407. proc Addr_Entry_UnsetExcluded {key} {
  408.     global addr_list addr_db
  409.  
  410.     if [catch {set item $addr_list($key)} err] {
  411.         Exmh_Status "Address DB: Addr_Entry_UnsetExcluded lookup error for $key"
  412.     } else {
  413.         if {5 == [llength $item]} {
  414.             set addr_list($key) [lrange $item 0 3]
  415.             set addr_db(changed) 1
  416.         }
  417.     }
  418.     return 
  419. }
  420.  
  421. proc Addr_Entry_ToggleExcluded {key} {
  422.     if [Addr_Entry_IsExcluded $key] {
  423.         Addr_Entry_UnsetExcluded $key
  424.     } else {
  425.         Addr_Entry_SetExcluded $key
  426.     }
  427. }
  428.  
  429.  
  430. # Format an address with full name.  The result may be either
  431. #
  432. #       address (full name)
  433. # or
  434. #       full name <address>
  435. #
  436. # depending on the state of the standard_address_format flag.
  437. proc Addr_Entry_FormatForMail {key} {
  438.     global addr_list addr_db
  439.  
  440.     if [catch {set item $addr_list($key)} err] {
  441.         return $key
  442.     }
  443.  
  444.     set fullname  [lindex $item 2]
  445.     regsub ,$ $fullname {} fullname_less_comma
  446.     if {0 == [string length "$fullname_less_comma" ]} {
  447.         set formatted "$key"
  448.     } else {
  449.         if {$addr_db(standard_address_format)} {
  450.             set formatted "$key ($fullname_less_comma)"
  451.         } else {
  452.             # If there are characters in the name that require quoting,
  453.             # quote the string.
  454.             if [string match {*[<>.,'*?]*} $fullname_less_comma] {
  455.                 set formatted "\"$fullname_less_comma\" <$key>"
  456.             } else {
  457.                 set formatted "$fullname_less_comma <$key>"
  458.             }
  459.         }
  460.     }
  461.     
  462.     return $formatted
  463.  
  464. }
  465.  
  466. proc Addr_Entry_FormatForListbox {key} {
  467.     global addr_list
  468.  
  469.     if [catch {set item $addr_list($key)} err] {
  470.         return $key
  471.     }
  472.  
  473.     set fullname  [lindex $item 2]
  474.     regsub ,$ $fullname {} fullname_less_comma
  475.     if [Addr_Entry_IsExcluded $key] {
  476.         set formatted [format "%-24.24s- <%s>" $fullname_less_comma $key ]
  477.     } else {
  478.         set formatted [format "%-25.25s <%s>" $fullname_less_comma $key ]
  479.     }
  480.  
  481.     return $formatted
  482. }
  483.  
  484. #
  485. # Addr_Save updates the database entry when a new message is read.
  486. #
  487.  
  488. proc Addr_Save {from path rawfrom date {forcesave "not"}} {
  489.     global addr_db
  490.     global addr_list
  491.     global exmh
  492.     global mhProfile
  493.  
  494.     if {[string compare $forcesave "force"] != 0} {
  495.         if {$addr_db(skip_folders) != ""} {
  496.             set filter_list [split $addr_db(skip_folders) " "]
  497.             # AddrDebug "  folder filter list is \"$filter_list\""
  498.             foreach i $filter_list {
  499.                 if {[string length $i] > 0} {
  500.                     # AddrDebug "Matching \"$exmh(folder)\" for \"$i\""
  501.                     if [string match $i $exmh(folder)] {
  502.                         AddrDebug "  folder filter eliminated $exmh(folder)"
  503.                         return
  504.                     }
  505.                 }
  506.             }
  507.         }
  508.         if {$addr_db(filter_alternate_mailboxes) != 0} {
  509.             catch {unset filter_list}
  510.             catch {
  511.                 set filter_list [split $mhProfile(alternate-mailboxes) ", "]
  512.             }
  513.             if [info exists filter_list] {
  514.                 # Filter out all of the user's alternate mailboxes
  515.                 # AddrDebug "alternate mailboxes \"$mhProfile(alternate-mailboxes)"
  516.                 set filter_list [split $mhProfile(alternate-mailboxes) ", "]
  517.                 # AddrDebug "  list \"$filter_list\""
  518.                 foreach i $filter_list {
  519.                     if {[string length $i] > 0} {
  520.                         if [string match $i $from] {
  521.                             AddrDebug "  alternate mailbox filter eliminated $from"
  522.                             return
  523.                         }
  524.                     }
  525.                 }
  526.             }
  527.         }
  528.         if {$addr_db(filter_regexp) != ""} {
  529.             # AddrDebug "filtering $addr_db(filter_regexp)"
  530.             if [regexp -nocase -- $addr_db(filter_regexp) $from] {
  531.                 AddrDebug "  regexp filter eliminated $from"
  532.                 return
  533.             }
  534.         }
  535.     }
  536.     set addr_db(changed) 1
  537.     set newentry 0
  538.     if [info exists addr_list($from)] {
  539.         Exmh_Status "Updating address \"$from\"."
  540.         if {[string length [Addr_ParseFrom $rawfrom]] == 0} {
  541.             set newone [list $path $date  \
  542.                     [lindex $addr_list($from) 2] $rawfrom [Addr_Entry_IsExcluded $from]]
  543.         } else {
  544.             set newone [list $path $date  \
  545.                     [Addr_ParseFrom $rawfrom] $rawfrom [Addr_Entry_IsExcluded $from]]
  546.         }
  547.     } else {
  548.         Exmh_Status "Saving address \"$from\"."
  549.         set newone [list $path $date  \
  550.                 [Addr_ParseFrom $rawfrom] $rawfrom] 
  551.         set newentry 1
  552.     }
  553.     set addr_list($from) $newone
  554.     if {$newentry} {
  555.         # Gotta catch this in case there's no browser window
  556.         catch {
  557.             $addr_db(win) insert end [Addr_Entry_FormatForListbox $from]
  558.         }
  559.     }
  560. }
  561.  
  562. #
  563. # Addr_KeyExpand expands a partial address in response to a key binding
  564. #
  565. proc Addr_KeyExpand { w } {
  566.     global addr_db
  567.  
  568.     AddrDebug "Addr_KeyExpand: w=$w $addr_db(searchlist)"
  569.     set line [string trimright [$w get {insert linestart} {insert lineend}]]
  570.     ##  AddrDebug "  got line \"$line\""
  571.     # Only allows expansion on addressable header lines.
  572.     #old regexp:  regexp -nocase {^(to: *|resent-to: *|cc: *|resent-cc: *|bcc: *|dcc: *)(.*)} $line t0 t1 t2
  573.     if {[$w compare insert <= hlimit] && \
  574.             [regexp -nocase {^([-a-z]+: *)?(.*)} $line t0 t1 t2]} {
  575.         ##  AddrDebug  "  matched! keep is \"$t1\", partial name=\"$t2\""
  576.         if {[regexp -indices ",?.*, *" $t2 t0] || \
  577.                 [regexp -indices "^ +" $t2 t0]} {
  578.             set t0 [lindex $t0 end]
  579.             ##  AddrDebug "got comma at $t0"
  580.             set t3 [string range $t2 0 $t0]
  581.             append t1 $t3
  582.             set t2 [string range $t2 [expr $t0 + 1] end]
  583.             ##  AddrDebug "  multi, will keep \"$t1\", new partial name=\"$t2\""
  584.         }
  585.         if {[string compare $addr_db(lastfound) $t2] != 0 \
  586.                 || $addr_db(curmethod) >= [llength $addr_db(searchlist)]} {
  587.             Exmh_Status "Resetting start method"
  588.             catch {destroy $w.addrs}
  589.             set addr_db(expansion) {}
  590.             set addr_db(curmethod) 0
  591.             set addr_db(laststring) $t2
  592.         } else {
  593.             if {$addr_db(curmethod) != 0} {
  594.                 set t2 $addr_db(laststring)
  595.             }
  596.         }
  597.         foreach proc [lrange $addr_db(searchlist) $addr_db(curmethod) end] {
  598.             incr addr_db(curmethod)
  599.             Exmh_Status "$proc $t2"
  600.             set result [busy $proc $t2]
  601.             if {[string compare $result ""] == 0} continue
  602.             if {[llength $result] == 1} {
  603.                 # unique match
  604.                 $w delete  {insert linestart} {insert lineend}
  605.                 $w insert insert [format "%s%s" $t1 [lindex $result 0]]
  606.                 set addr_db(lastfound) [lindex $result 0]
  607.                 catch {destroy $w.addrs}
  608.             } else {
  609.                 # must be multiple hits
  610.                 AddrDebug "  Multiple hits: $result"
  611.                 set addr_db(lastfound) $t2
  612.                 set new [AddrShowDialog $w $result]
  613.                 # if no selection is made, leave the string where it is
  614.                 if [ string compare $new "" ] {
  615.                     set addr_db(lastfound) $new
  616.                     $w delete  {insert linestart} {insert lineend}
  617.                     $w insert insert [format "%s%s" $t1 $new]
  618.                 }
  619.             }
  620.             break
  621.         }
  622.     } else {
  623.         Exmh_Status "Error in name expansion: not on supported field"
  624.         return
  625.     }
  626. }
  627.  
  628. proc Alias_Lookup {n} {
  629.     global aliases
  630.     Aliases_Load
  631.     set t2 [string trim $n]
  632.     if {[string length [array names aliases $t2]] == 0} {
  633.         Exmh_Status "No match found for \"$t2\""
  634.         return {}
  635.     } else {
  636.         Exmh_Status "Found alias: \"$aliases($t2)\""
  637.         if {1 == [llength $aliases($t2)] && \
  638.             0 != [string compare "\{" [string range $aliases($t2) 0 0]] } {
  639.             return [list [Addr_Entry_FormatForMail $aliases($t2)]]
  640.         }
  641.         # Note: cannot use Address_Entry_FormatForMail here since contents
  642.         # of alias is too unpredicatble.  May be a list of names, may be 
  643.         # a preformatted fullname and address.  So send it back as-is
  644.         return $aliases($t2)
  645.     }
  646. }
  647.  
  648. proc Addr_FullNameMatch {n}  {
  649.     global addr_list
  650.  
  651.     Exmh_Status "Matching on full names with $n..."
  652.     set result {}
  653.     set pat {}
  654.     append pat [string trim $n]
  655.     foreach i [ array names addr_list] {
  656.         if {1 == [Addr_Entry_IsExcluded $i]} continue
  657.         set elt $addr_list($i)
  658.         set fn [lindex $elt 2]
  659.         # puts stdout "matching against $fn (elt = $elt)"
  660.         if [catch {set match [regexp -nocase -- $pat $fn t0]}] {
  661.             Exmh_Status "Fullname expansion error: Invalid regexp \"$pat\""
  662.             return {}
  663.         }
  664.         if {$match} {
  665.             AddrDebug "   fullname match on $fn"
  666.             lappend result "[Addr_Entry_FormatForMail $i]"
  667.         }  
  668.     }
  669.     if {[llength $result] > 0} {
  670.         return $result
  671.     } else {
  672.         Exmh_Status "Matching on full names with \"$pat\"...none found"
  673.         return {}
  674.     }
  675. }
  676.  
  677.     
  678. proc LDAP_Lookup {n} {
  679.     global addr_db
  680.  
  681.     # Make sure the ldap_server variable has been set in the preferences.
  682.     if { ($addr_db(ldap_server) == {}) || ($addr_db(ldap_searchbase) == {}) } {
  683.         return {}
  684.     }
  685.     
  686.     Exmh_Status "Querying $addr_db(ldap_server) from $addr_db(ldap_searchbase) with $n..."
  687.  
  688.     set query "(|(cn=*$n*)(mail=*$n*)(sn=*$n*)(givenname=*$n*))"
  689.     if {[catch {set query "[encoding convertto $addr_db(ldap_encoding) "$query"]"} err]} {
  690.     Exmh_Debug "LDAP_Lookup encoding convertto: $err"
  691.     }
  692.     if [catch {set ldap_results [exec ldapsearch -B -h [string trim $addr_db(ldap_server)] \
  693.                                                  -b $addr_db(ldap_searchbase) \
  694.                                                  "$query" cn mail]} err] {
  695.         Exmh_Status "Error executing ldapsearch: $err"
  696.         return {}
  697.     }
  698.  
  699.     if {[catch {set ldap_results [encoding convertfrom utf-8 "$ldap_results"]} err]} {
  700.     Exmh_Debug "LDAP_Lookup encoding convertfrom: $err"
  701.     }
  702.     # The return from ldapsearch will be something like this:
  703.     #
  704.     # cn=Lastname, Firstname
  705.     # mail=foo@nowhere.com
  706.     #
  707.     # cn=Anotherlastname, Anotherfirstname
  708.     # mail=bar@nowhere.com
  709.     #
  710.     # ...
  711.     set result {}
  712.     foreach i [split $ldap_results \n ] {
  713.         if [regexp -nocase {^mail[=:] *([^,]*)$} $i dummy email] {
  714.             lappend result "[LDAP_Entry_FormatForMail $email $name]"
  715.         } elseif [regexp -nocase {^cn[=:] *(.*)$} $i dummy tmp] {
  716.             set name $tmp
  717.         }
  718.     }
  719.     
  720.     return $result
  721. }
  722.  
  723.  
  724. proc LDAP_Entry_FormatForMail { email name } {
  725.     global addr_db
  726.     
  727.     if {$addr_db(standard_address_format)} {
  728.         set formatted "$email ($name)"
  729.     } else {
  730.         # If there are characters in the name that require quoting,
  731.         # quote the string.
  732.         if [string match {*[<>.,'*?]*} $name] {
  733.             set formatted "\"$name\" <$email>"
  734.         } else {
  735.             set formatted "$name <$email>"
  736.         }
  737.     }
  738.  
  739.     return $formatted
  740. }
  741.  
  742. proc Addr_Lookup { n } {
  743.     global addr_list
  744.  
  745.     AddrDebug "Addr_Lookup: looking for $n"
  746.     if {[string compare $n ""] == 0} {
  747.         Exmh_Status "Address expansion error: null string!"
  748.         return {}
  749.     }
  750.  
  751.     set result {}
  752.     set pat {}
  753.     append pat [string trim $n]
  754.     AddrDebug "  using pattern \"$pat\""
  755.  
  756.     foreach i [array names addr_list] {
  757.         if {1 == [Addr_Entry_IsExcluded $i]} continue
  758.         set elt $addr_list($i)
  759.         if [catch {set match [regexp -nocase -- $pat $i t0]}] {
  760.             Exmh_Status "Address expansion error: Invalid regexp \"$pat\""
  761.             return {}
  762.         }
  763.         if {$match} {
  764.             AddrDebug "   match on $i"
  765.             set fn [lindex $elt 2]
  766.             lappend result "[Addr_Entry_FormatForMail $i]"
  767.         }
  768.     }
  769.  
  770.     AddrDebug "Addr_Lookup: result is $result"
  771.     return $result
  772. }
  773.  
  774.  
  775. #
  776. # Addr_ParseFrom takes a raw From: header and return the fullname.
  777. # it should work on lines of the form:
  778. #
  779. ## Berry Kercheval <kerch@parc.xerox.com>
  780. ## kerch@parc.xerox.com (Berry Kercheval)
  781.  
  782. proc Addr_ParseFrom { fromline } {
  783.     #    AddrDebug "Addr_ParseFrom: working on $fromline"
  784.     set line [string trim $fromline]
  785.  
  786.     # if it's "xxx <foo@bar>"...
  787.     if [regexp {([^<]*)(<.*>)} $line t1 t2 t3] {
  788.         #       AddrDebug "  Matched: ( $t1 )( $t2 )( $t3 )"
  789.         set token [string trim $t2 ]
  790.     } else {
  791.         # nope, try foo@bar (xxx)
  792.         #       AddrDebug "  Not xxx <foo@bar>, try foo@bar (xxx)"
  793.         if [regexp {^([^\(]*)(\(.*\))[^\)]*$} $line t1 t2 t3] {
  794.             #       AddrDebug "  Matched: ( $t1 )( $t2 )( $t3 )"
  795.             set token $t3
  796.         } else {
  797.             # none of the above, give up.
  798.             set token {}
  799.         }
  800.     }
  801.     
  802.     #    AddrDebug "  result is $token"
  803.     #    set token [string trim $token "\"()"]
  804.     if [regexp {^\((.*)\)$} $token t1 t2] {
  805.        set token $t2
  806.     } 
  807.     if [regexp {^\"(.*)\"$} $token t1 t2] {
  808.        set token $t2
  809.     }
  810.     #    AddrDebug "  trimmed result is $token"
  811.     return $token
  812. }
  813.  
  814. #
  815. # Debug support
  816. #
  817.  
  818. proc AddrDebug { s {nonewline {}}} {
  819.     global Addr_debug
  820.     if {$Addr_debug == 1} {
  821.         if {[string compare $nonewline ""] == 0} {
  822.             puts stdout $s 
  823.         } else {
  824.             puts stdout $s nonewline
  825.         }
  826.     }
  827. }
  828.  
  829. proc AddrShowDialog {w list} {
  830.     global addr_db 
  831.  
  832.     catch {destroy $w.addrs}
  833.     set f [frame $w.addrs -bd 4 -relief ridge]
  834.     frame $f.top -relief flat
  835.     set l [listbox $f.top.lb -bd 4 -width 50 -height 10]
  836.     bind $l <Any-Double-1> "\
  837.             AddrShowDialogDone $f $l ;\
  838.             break \
  839.             "
  840.     bind $l <Escape> "\
  841.             AddrShowDialogCancel $f ;\
  842.             break \
  843.             "
  844.     focus $f.top.lb
  845.     $f configure -cursor left_ptr
  846.     foreach i $list {
  847.         $l insert end $i
  848.     }
  849.     pack $f.top.lb -expand true -fill both -side left
  850.     if {[llength $list] > 10} {
  851.         $f.top.lb configure -yscrollcommand "$f.top.sy set"
  852.         scrollbar $f.top.sy -width 15 -command [list $l yview]
  853.         pack $f.top.sy -expand true -fill y -side left
  854.     }
  855.     pack $f.top -expand true -fill both
  856.     frame $f.but -bd 10 -relief flat
  857.     pack $f.but -expand true -fill both
  858.     Widget_AddBut $f.but ok "Done" [list AddrShowDialogDone $f $l] {left filly}
  859.     Widget_AddBut $f.but can "Cancel" [list AddrShowDialogCancel $f] {right filly}
  860.     Widget_PlaceDialog $w $f
  861.     tkwait window $f
  862.     if [info exists addr_db(expansion)] {
  863.         Exmh_Status "returning $addr_db(expansion)"
  864.         return $addr_db(expansion)
  865.     } else {
  866.         return {}
  867.     }
  868. }
  869.  
  870. proc AddrShowDialogDone {f l} {
  871.     global addr_db
  872.     set result [$l curselection]
  873.     if {[string compare $result ""] != 0} {
  874.         set result [lindex $result 0]
  875.         set name [$l get $result]
  876.         AddrDebug "Selected: $result ($name)"
  877.         set addr_db(expansion) $name
  878.     } else {
  879.         AddrDebug "Selected: <nothing>"
  880.         set addr_db(expansion) ""
  881.     }
  882.     AddrDebug "selected $addr_db(expansion)"
  883.     focus [winfo parent $f]
  884.     catch {destroy $f}
  885. }
  886.  
  887. proc AddrShowDialogCancel {f} {
  888.     global addr_db
  889.  
  890.     set addr_db(expansion) ""
  891.     AddrDebug "selected $addr_db(expansion)"
  892.     focus [winfo parent $f]
  893.     catch {destroy $f}
  894. }
  895.  
  896.  
  897. proc Addr_Browse { {state normal} } {
  898.     global exwin
  899.     global addr_br 
  900.     global addr_db 
  901.     global addr_list
  902.     global Addr_debug
  903.  
  904.     set t .addr_br
  905.     set f .addr_br.but
  906.     set ldmsg "Creating Address Browser..."
  907.     if [Exwin_Toplevel .addr_br "Address DB Browser" Addr_Br] {
  908.         # Reconfigure the Dismiss button created by Exwin_Toplevel
  909.         $f.quit configure -takefocus {} -command {Exwin_Dismiss .addr_br}
  910.  
  911.         # Create the "Selected..." menu (initially disabled)
  912.         set menu_sel [Widget_AddMenuB $f selmenu "Selected..." {right padx 1 filly} ]
  913.         $f.selmenu configure -takefocus {} -state disabled
  914.         set addr_db(selmenu) $f.selmenu
  915.         Widget_AddMenuItem $menu_sel  "Mail To"           \
  916.                 { Addr_Browse_Selected MailTo } <Key-c>
  917.         Widget_AddMenuItem $menu_sel  "Edit"           \
  918.                 { Addr_Browse_Selected Edit }
  919.         Widget_AddMenuItem $menu_sel  "Delete"         \
  920.                 { Addr_Browse_Selected Delete } <Control-w>
  921.         Widget_AddMenuItem $menu_sel  {Toggle Exclude} \
  922.                 { Addr_Browse_Selected Exclude } <Meta-x>
  923.         Widget_AddMenuItem $menu_sel  {View Last Msg}  \
  924.                 { Addr_Browse_Selected ViewLastMsg }
  925.  
  926.         # Create the "Database..." menu
  927.         set menu_db [Widget_AddMenuB $f dbmenu "Database..." {right padx 1 filly} ]
  928.         $f.dbmenu configure -takefocus {}
  929.         Widget_AddMenuItem $menu_db   "Save"   \
  930.                 { Addr_SaveFile 1 } <Meta-s>
  931.         Widget_AddMenuItem $menu_db   "Reload" \
  932.                 { Addr_Browse_Reload } <Meta-r>
  933.         Widget_AddMenuItem $menu_db   "Sort"   \
  934.                 { Addr_Browse_LoadListbox "Sorting database..." normal } <Meta-t>
  935.         if { $Addr_debug == 1 }  { Widget_AddBut $f ldsrc  "LdSrc"  { Addr_Load_Source } }
  936.  
  937.         # Create the New button
  938.         Widget_AddBut $f new   "New"   { Addr_Browse_New }
  939.  
  940.         # Finally, create the Help button
  941.         Widget_AddBut $f help   "Help"   { Help AddrEdit }
  942.         $f.help configure -takefocus {}
  943.  
  944.         # would be nice if the listbox was a set of coordinated list boxes,
  945.         # one column for each field, with headings and options to pick which
  946.         # to display and which to sort on.
  947.  
  948.         # Create the listbox and a scroll bar to help it
  949.         set addr_db(win) [listbox $t.lb \
  950.                 -selectmode extended \
  951.                 -height 20 -width 65 \
  952.                 -relief sunken \
  953.                 -yscrollcommand [list $t.sb set] ]
  954.         scrollbar $t.sb -orient vertical -command [list $addr_db(win) yview]
  955.  
  956.         # Create the filter/find entry field
  957.         Addr_LabelledTextField $t.find Find 0  "set addr_db(filterstring) \[$t.find.entry get \]; Addr_Browse_LoadListbox {Finding...} normal"
  958.         $t.find.entry insert 0 $addr_db(filterstring)
  959.  
  960.         # Mouse button bindings for the listbox
  961.         bind $addr_db(win) <Any-Double-1>    {Addr_Browse_Selected Edit}
  962.         bind $addr_db(win) <KeyRelease>      {Addr_Browse_TrackSel}
  963.         bind $addr_db(win) <ButtonRelease>   {Addr_Browse_TrackSel}
  964.         bind $addr_db(win) <Button-2>        {Addr_Browse_Selected Exclude}
  965.  
  966.         # Menu key accelerators for the toplevel, 
  967.         # but don't do 'em if in the find entry field
  968.         bind $t <Meta-x> {
  969.             if {0 != [string compare "%W" ".addr_br.find.entry"]} {Addr_Browse_Selected Exclude}
  970.         }
  971.         bind $t <Key-c> {
  972.             if {0 != [string compare "%W" ".addr_br.find.entry"]} {Addr_Browse_Selected MailTo}
  973.         }
  974.         bind $t <Control-w> {
  975.             if {0 != [string compare "%W" ".addr_br.find.entry"]} {Addr_Browse_Selected Delete}
  976.         }
  977.         bind $t <Meta-s> {
  978.             if {0 != [string compare "%W" ".addr_br.find.entry"]} {Addr_SaveFile 1}
  979.         }
  980.         bind $t <Meta-r> {
  981.             if {0 != [string compare "%W" ".addr_br.find.entry"]} {Addr_Browse_Reload}
  982.         }
  983.  
  984.         # Adjust packing and filling
  985.         pack $t.find -side bottom -fill x
  986.         pack $t.sb -side $exwin(scrollbarSide) -fill y
  987.         pack $addr_db(win) -expand true -fill both
  988.     }
  989.  
  990.     if {0 == [string compare "$state" "normal"]} { 
  991.         Exmh_Status $ldmsg
  992.     }
  993.  
  994.     # All built, now load up the listbox
  995.     Addr_Browse_LoadListbox $ldmsg $state
  996.     
  997.     # Initial focus to the listbox so accelerators work.
  998.     focus $addr_db(win)
  999.     if {0 == [string compare "$state" "normal"]} { 
  1000.         Exmh_Status "$ldmsg done" 
  1001.     }
  1002. }
  1003.  
  1004. proc Addr_Browse_TrackSel {} {
  1005.     global addr_db
  1006.  
  1007.     catch {     ;# windows may not exist
  1008.         if { 0 != [string length [$addr_db(win) curselection]] } {
  1009.             $addr_db(selmenu) configure -state normal
  1010.         } else {
  1011.             $addr_db(selmenu) configure -state disabled
  1012.         }
  1013.     }
  1014. }
  1015.  
  1016. proc Addr_Browse_LoadListbox { {ldmsg ""} {state normal}} {
  1017.     global addr_db addr_list
  1018.  
  1019.     if {![info exists addr_db(win)] ||
  1020.         ![winfo exists $addr_db(win)]}  return
  1021.  
  1022.     if {[catch {regexp -nocase -- $addr_db(filterstring) {}} err]} {
  1023.         Exmh_Status $err        ;# bad pattern
  1024.         return
  1025.     }
  1026.  
  1027.     $addr_db(win) delete 0 end
  1028.  
  1029.     if {0 == [string compare "$state" "normal"]} { 
  1030.         Exmh_Status "$ldmsg getting names..."
  1031.     }
  1032.  
  1033.     set l {}
  1034.     foreach i [array names addr_list] {
  1035.         if {$addr_db(hideexcluded) == 0 || [Addr_Entry_IsExcluded $i] == 0} {
  1036.             lappend l [Addr_Entry_FormatForListbox $i]
  1037.         }
  1038.     }
  1039.  
  1040.     if {[llength $l]} {
  1041.         if {0 == [string compare "$state" "normal"]} { 
  1042.             Exmh_Status "$ldmsg sorting names..."
  1043.         }
  1044.         set l [lsort $l]
  1045.         set n 0
  1046.         set whiz [list | \\ - /]
  1047.         set w 0
  1048.         if {[string length $addr_db(filterstring)] > 0} {
  1049.             foreach i $l {
  1050.                 if [regexp -nocase -- $addr_db(filterstring) $i] {
  1051.                     $addr_db(win) insert end $i
  1052.                 }
  1053.                 incr n
  1054.                 if { 0==($n%100) } {
  1055.                     if {0 == [string compare "$state" "normal"]} { 
  1056.                         Exmh_Status "$ldmsg inserting names... [lindex $whiz $w]"
  1057.                     }
  1058.                     set w [expr {($w+1)%4}]
  1059.                 }
  1060.             }
  1061.         } else {
  1062.             foreach i $l {
  1063.                 $addr_db(win) insert end $i
  1064.                 incr n
  1065.                 if { 0==($n%100) } {
  1066.                     if {0 == [string compare "$state" "normal"]} { 
  1067.                         Exmh_Status "$ldmsg inserting names... [lindex $whiz $w]"
  1068.                     }
  1069.                     set w [expr {($w+1)%4}]
  1070.                 }
  1071.             }
  1072.         }
  1073.     }
  1074.     if {0 == [string compare "$state" "normal"]} { 
  1075.         Exmh_Status  "$ldmsg done"
  1076.         update idletasks 
  1077.     }
  1078. }
  1079.  
  1080. # Called by trace variable magic when user changes Show Excluded preference item
  1081. proc Addr_Browse_Exclude_Change {name element op} {
  1082.     global addr_db
  1083.     catch {Exmh_Debug Event addr_db win is $addr_db(win)}
  1084.     Addr_Browse_LoadListbox {} silently
  1085.     return
  1086. }
  1087.  
  1088. proc Addr_Browse_Clip {sel} {
  1089.         global addr_db addr_list mhProfile
  1090.  
  1091.     set victim [MsgParseFrom [$addr_db(win) get $sel]]
  1092.     set last [lindex $addr_list($victim) 0]
  1093.  
  1094.     # since there may be recursive folders, match the MH Path
  1095.     # off the front, the message number off the end and the rest must be the folder.
  1096.     set pat "($mhProfile(path))/(.+)/(\[0-9\]+)\$"
  1097.  
  1098.     if [regexp -- $pat $last match path folder msg] {
  1099.         Msg_Clip $folder $msg
  1100.     } else {
  1101.         Exmh_Status "ViewLastMsg cannot find $last"
  1102.     }
  1103. }
  1104.  
  1105. proc Addr_Browse_Reload {} {
  1106.     global addr_db addr_list
  1107.  
  1108.     set ldmsg "Reloading address database..."
  1109.     if $addr_db(changed) {
  1110.  
  1111.         if [Addr_Browse_ChangedDialog $addr_db(win)] {
  1112.  
  1113.             Exmh_Status $ldmsg
  1114.             unset addr_list
  1115.             Addr_LoadDB $ldmsg
  1116.             Exmh_Status "$ldmsg done."
  1117.         }  else {
  1118.             Exmh_Status "$ldmsg aborted."
  1119.         }
  1120.     } else { 
  1121.         Exmh_Status $ldmsg
  1122.         unset addr_list
  1123.         Addr_LoadDB $ldmsg
  1124.         Exmh_Status "$ldmsg done."
  1125.     }
  1126. }
  1127.  
  1128. proc Addr_Browse_ChangedDialog {w} {
  1129.     global addr_db
  1130.     set f [frame $w.addrch -bd 4 -relief ridge]
  1131.     Exmh_Status "$f"
  1132.     Widget_AddBut $f ok "Yes, this will lose all changes" "\
  1133.             set addr_db(changeresult) 1 ;\
  1134.             destroy $f " \
  1135.             left
  1136.     Widget_AddBut $f no "No, do not reload" "\
  1137.             set addr_db(changeresult) 0 ;\
  1138.             destroy $f " \
  1139.             right
  1140.     Widget_PlaceDialog $w $f
  1141.     tkwait window $f
  1142.     AddrDebug "Addr_Browse_ChangedDialog returns  $addr_db(changeresult)"
  1143.     return $addr_db(changeresult)
  1144. }
  1145.  
  1146. proc Addr_LabelledTextField { name label width command  } { 
  1147.     frame $name
  1148.     label $name.label -text $label -width $width -anchor w
  1149.     eval {entry $name.entry -relief sunken -width  50 } 
  1150.     pack $name.label -side left
  1151.     pack $name.entry -side right -fill x -expand true
  1152.     bind $name.entry <Return> "$command ; break"
  1153.     return $name.entry
  1154. }
  1155.  
  1156. # Interate across all selected items applying a command
  1157. proc Addr_Browse_Selected { { op Noop } } {
  1158.     global addr_db addr_list
  1159.  
  1160.     set to {} ; set sep ""
  1161.  
  1162.     foreach sel [lsort -decreasing -integer [$addr_db(win) curselection]] {
  1163.  
  1164.         switch $op {
  1165.  
  1166.             MailTo {
  1167.                 append to $sep[Addr_Entry_FormatForMail \
  1168.                     [MsgParseFrom [$addr_db(win) get $sel]]]
  1169.                 set sep ", "
  1170.             }
  1171.  
  1172.             Edit {
  1173.                 Addr_Browse_Edit $sel
  1174.             }
  1175.  
  1176.             Delete {
  1177.                 set victim [MsgParseFrom [$addr_db(win) get $sel]]
  1178.                 if [catch {unset addr_list($victim)} val] {
  1179.                     Exmh_Status "Address DB: can't delete: $val"
  1180.                 } else {
  1181.                     set addr_db(changed) 1
  1182.                     # update browser window...
  1183.                     $addr_db(win) delete $sel
  1184.                     Exmh_Status "Address DB: Deleted $victim"
  1185.                 }
  1186.             }
  1187.  
  1188.             Exclude {
  1189.                 set victim [MsgParseFrom [$addr_db(win) get $sel]]
  1190.                 Addr_Entry_ToggleExcluded $victim
  1191.                 # update browser window...
  1192.                 $addr_db(win) delete $sel
  1193.                 if {$addr_db(hideexcluded) == 0 || [Addr_Entry_IsExcluded $victim] == 0} {
  1194.                     $addr_db(win) insert $sel [Addr_Entry_FormatForListbox $victim]
  1195.                 }
  1196.             }
  1197.  
  1198.             ViewLastMsg {
  1199.                 Addr_Browse_Clip $sel
  1200.             }
  1201.  
  1202.         }
  1203.     }
  1204.     if {[string length $to] > 0} {
  1205.         Msg_CompTo $to
  1206.     }
  1207.  
  1208.     Addr_Browse_TrackSel
  1209. }
  1210.  
  1211. proc Addr_Browse_Edit {sel} {
  1212.     global addr_db addr_list
  1213.  
  1214.     set victim [MsgParseFrom [$addr_db(win) get $sel]]
  1215.     if [catch {set item $addr_list($victim)} err] {
  1216.         Exmh_Status "Address DB: Addr_Browse_Edit lookup error for $victim"
  1217.         return
  1218.     }
  1219.  
  1220.     set name [lindex $item 2]
  1221.     set addr [lindex $item 3]
  1222.     set last [lindex $item 0]
  1223.     set date [lindex $item 1]
  1224.     set exclude [Addr_Entry_IsExcluded $victim]
  1225.     unset item
  1226.  
  1227.     set t .addr_ed
  1228.     set id 0
  1229.     for {set id 1} {$id < 21} {incr id} {
  1230.         AddrDebug "winfo exists $t$id is [winfo exists $t$id]"
  1231.         if [winfo exists $t$id] continue
  1232.         append t $id
  1233.         break
  1234.     }
  1235.     if [winfo exists $t] {
  1236.         Exmh_Status "Too many editors open, close one or more and try again"
  1237.         return
  1238.     }
  1239.     if [Exwin_Toplevel $t "Address DB editor" Addr_Ed] {
  1240.  
  1241.         set f $t.but
  1242.  
  1243.         $t.but.quit configure -text Cancel -command "Addr_Edit_Dismiss $t"
  1244.         $t configure -width 500
  1245.  
  1246.         Widget_AddBut $f save   "Save"            "Addr_Edit_Save $t $sel"
  1247.         Widget_AddBut $f delete "Delete"          "Addr_Edit_Delete $t $sel"
  1248.         Widget_AddBut $f last   "ViewLastMsg"     "Addr_Browse_Clip $sel"
  1249.         set e [Widget_AddBut $f ignore  "Exclude" "Addr_Edit_Exclude $t $sel"]
  1250.  
  1251.         set n [Addr_LabelledTextField $t.name     "Full Name"     12 "Addr_Edit_Save $t $sel" ]
  1252.         set a [Addr_LabelledTextField $t.address  "Address"       12 "Addr_Edit_Save $t $sel" ]
  1253.         set l [Addr_LabelledTextField $t.lastMsg  "Last Message"  12 "Addr_Edit_Save $t $sel" ]
  1254.         set d [Addr_LabelledTextField $t.date     "Date"          12 "Addr_Edit_Save $t $sel" ]
  1255.         
  1256.         pack $t.name $t.address  $t.lastMsg  $t.date
  1257.  
  1258.     }
  1259.  
  1260.     $n delete 0 end;    $n insert 0 $name
  1261.     $a delete 0 end;    $a insert 0 $addr
  1262.     $l delete 0 end;    $l insert 0 $last
  1263.     $d delete 0 end;    $d insert 0 $date
  1264.         
  1265.     if {$exclude == 1} {
  1266.         $e config -text "Include"
  1267.         AddrDebug "$e config -text Include"
  1268.     } else {
  1269.         $e config -text "Exclude"
  1270.         AddrDebug "$e config -text Exclude"
  1271.     }
  1272. }
  1273.  
  1274.  
  1275.  
  1276. proc Addr_Browse_New {} {
  1277.     global addr_db addr_list
  1278.  
  1279.     set t .addr_ed
  1280.     set id 0
  1281.     for {set id 1} {$id < 21} {incr id} {
  1282.         AddrDebug "winfo exists $t$id is [winfo exists $t$id]"
  1283.         if [winfo exists $t$id] continue
  1284.         append t $id
  1285.         break
  1286.     }
  1287.     if [winfo exists $t] {
  1288.         Exmh_Status "Too many editors open, close one or more and try again"
  1289.         return
  1290.     }
  1291.     if [Exwin_Toplevel $t "New DB address" Addr_Ed] {
  1292.  
  1293.         set f $t.but
  1294.  
  1295.         $t.but.quit configure -text Cancel -command "Addr_Edit_Dismiss $t"
  1296.         $t configure -width 500
  1297.  
  1298.         Widget_AddBut $f save "Save"      "Addr_New_Save $t"
  1299.  
  1300.         Addr_LabelledTextField $t.name    "Full Name" 12 "Addr_New_Save $t"
  1301.         Addr_LabelledTextField $t.address "Address"   12 "Addr_New_Save $t"
  1302.  
  1303.         pack $t.name $t.address
  1304.     }
  1305. }
  1306.  
  1307. proc Addr_New_Save {winname} {
  1308.     global addr_db addr_list
  1309.  
  1310.     set name [$winname.name.entry get]
  1311.     set addr [$winname.address.entry get]
  1312.     set last ""
  1313.     set date ""
  1314.  
  1315.     set index [MsgParseFrom $addr]
  1316.     # NEED TO STUFF new NAME into entry!
  1317.     set addr [format "%s <%s>" $name $index]
  1318.     Exmh_Status "Updating address \"$index\"."
  1319.     set addr_db(changed) 1
  1320.     set addr_list($index) [list $last $date  \
  1321.             [Addr_ParseFrom $addr] $addr] 
  1322.  
  1323.     # update browser window...
  1324.     $addr_db(win) insert end [Addr_Entry_FormatForListbox $index]
  1325.  
  1326.     # make it all go away so we can redo it next time.
  1327.     Addr_Edit_Dismiss $winname
  1328. }
  1329.   
  1330.   
  1331. proc Addr_Load_Source {}  {
  1332.     global env
  1333.     # HACK HACK HACK!!!
  1334.     source "~/.tk/exmh/addr.tcl"
  1335. }
  1336.  
  1337. ########################################################################
  1338. #
  1339. # Address Editor Routines
  1340. #
  1341.  
  1342. proc Addr_Edit_Exclude {winname sel} {
  1343.     global addr_db addr_list
  1344.  
  1345.     set addr [$winname.address.entry get]
  1346.  
  1347.     set victim [MsgParseFrom $addr]
  1348.     Exmh_Status "Updating address \"$victim\"."
  1349.     Addr_Entry_ToggleExcluded $victim
  1350.     set exclude [Addr_Entry_IsExcluded $victim]
  1351.  
  1352.     # update browser window...
  1353.     $addr_db(win) delete $sel
  1354.     if {$addr_db(hideexcluded) == 0 || $exclude == 0} {
  1355.         $addr_db(win) insert $sel [Addr_Entry_FormatForListbox $victim]
  1356.     }
  1357.  
  1358.     # make it all go away so we can redo it next time.
  1359.     Addr_Edit_Dismiss $winname
  1360. }
  1361.  
  1362. proc Addr_Edit_Save {winname sel} {
  1363.     global addr_db addr_list
  1364.  
  1365.     set name [$winname.name.entry get]
  1366.     set addr [$winname.address.entry get]
  1367.     set last [$winname.lastMsg.entry get]
  1368.     set date [$winname.date.entry get]
  1369.  
  1370.     set index [MsgParseFrom $addr]
  1371.     # NEED TO STUFF new NAME into entry!
  1372.     set addr [format "%s <%s>" $name $index]
  1373.     Exmh_Status "Updating address \"$index\"."
  1374.     set addr_db(changed) 1
  1375.     set addr_list($index) [list $last $date  \
  1376.             [Addr_ParseFrom $addr] $addr] 
  1377.  
  1378.     # update browser window...
  1379.     $addr_db(win) delete $sel
  1380.     $addr_db(win) insert $sel [Addr_Entry_FormatForListbox $index]
  1381.  
  1382.     # make it all go away so we can redo it next time.
  1383.     Addr_Edit_Dismiss $winname
  1384. }
  1385.  
  1386. proc Addr_Edit_Delete { winname sel } {
  1387.     global addr_db addr_list
  1388.  
  1389.     set addr [$winname.address.entry get]
  1390.     set index [MsgParseFrom $addr]
  1391.     $addr_db(win) delete $sel
  1392.  
  1393.     unset addr_list($index)
  1394.     Addr_Edit_Dismiss $winname
  1395. }
  1396.  
  1397. proc Addr_Edit_Abort {} {
  1398.     global addr_db
  1399.     Addr_Edit_Dismiss
  1400. }
  1401.  
  1402. proc Addr_Edit_Dismiss { winname } {
  1403.     Exwin_Dismiss $winname
  1404.     destroy $winname
  1405. }
  1406. ########################################################################
  1407. #
  1408. # done loading...
  1409. #
  1410. if {$Addr_debug} { puts stdout "done." }
  1411.